/** @brief Test opening and closing a cursor on a table, running IUDS with the open cursor. 
 *
 *  testtable_cpp
 *  kangmodb
 *
 *  Created by 강모 김 on 11. 4. 11..
 *  Copyright 2011 강모소프트. All rights reserved.
 *
 */

#include "gtest/gtest.h"

#include "testCommon.h"
#include "table.h"
#include "set.h"
#include "gc.h"
#include "ptst.h"


/** @brief Test ISUD on a single table.]
 */
class TableTest : public testing::Test {
protected:  // You should make the members protected s.t. they can be
	// accessed from sub-classes.
	
	// virtual void SetUp() will be called before each test is run.  You
	// should define it if you need to initialize the varaibles.
	// Otherwise, this can be skipped.
	virtual void SetUp() {
		const int TABLE_ID = 100;
	
		_init_ptst_subsystem();
		_init_gc_subsystem();
		_init_set_subsystem();
		
		table_ = new stgTable();
		ASSERT_TRUE( table_ != NULL );
		ASSERT_TRUE( table_->initialize(TABLE_ID) == 0);
	}
	
	// virtual void TearDown() will be called after each test is run.
	// You should define it if there is cleanup work to do.  Otherwise,
	// you don't have to provide it.
	//
	virtual void TearDown() {
		ASSERT_TRUE( table_->destroy() == 0 );

		delete table_;
		table_ = NULL;
	
		_destroy_gc_subsystem();	
	}
protected:
	stgTable * table_;
};


/** @brief Test Insert/Update/Delete/Select on a table_
 */
TEST_F(TableTest, DISABLED_CursorIUDS) {
	char key[256];
	char value[256];

	const stgVersion VIEW_VERSION= 1004;
	const stgVersion VIEW_UNCOMITTED_VERSIONS = MAX_DATA_VERSION;
	stgData selectedValue;
	stgDataDesc * dataDesc;
	
	// insert 16 values with kmkim0dd
	for (int i=0; i<16; i++)
	{
		sprintf( key, "kmkim%03d", i);
		sprintf( value, "loves hyunsook %03d times", i);
		ASSERT_TRUE( table_->putDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->updateData( VIEW_VERSION, 0 /* savepoint Id */, dataDesc, KD_STR_DATA(value) ) == 0 );
	}
	
	// verify 16 values with kmkim0dd
	verify_table_key_values( VIEW_UNCOMITTED_VERSIONS, table_, 16, "kmkim%03d", "loves hyunsook %03d times");
					
	// insert 16 values with hscho0dd
	for (int i=0; i<16; i++)
	{
		sprintf( key, "hscho%03d", i);
		sprintf( value, "loves kangmo %03d times", i);
		ASSERT_TRUE( table_->putDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->updateData( VIEW_VERSION, 0 /* savepoint Id */, dataDesc, KD_STR_DATA(value) ) == 0 );
	}

	// verify 16 values with hscho0dd
	verify_table_key_values( VIEW_UNCOMITTED_VERSIONS, table_, 16, "hscho%03d", "loves kangmo %03d times");
									
	// update 16 values with kmkim0dd
	for (int i=0; i<16; i++)
	{
		sprintf( key, "kmkim%03d", i);
		sprintf( value, "loves and likes hyunsook %03d times", i);
		ASSERT_TRUE( table_->putDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->updateData( VIEW_VERSION, 0 /* savepoint Id */, dataDesc, KD_STR_DATA(value) ) == 0 );
	}

	// verify 16 values with kmkim0dd
	verify_table_key_values( VIEW_UNCOMITTED_VERSIONS, table_, 16, "kmkim%03d", "loves and likes hyunsook %03d times");

	// delete 16 keys with kmkim0dd 
	for (int i=0; i<16; i++)
	{
		sprintf( key, "kmkim%03d", i);
		ASSERT_TRUE( table_->putDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->setDeletedBit( VIEW_VERSION, 0 /* savepoint Id */, dataDesc, true ) == 0 );
	}
					
	// verify the deleted 16 keys do not exist.
	for (int i=0; i<16; i++)
	{
		stgData selectedData; 
		sprintf( key, "kmkim%03d", i);
		ASSERT_TRUE( table_->getDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->seekData( VIEW_UNCOMITTED_VERSIONS, dataDesc, & selectedData ) == KD_EXCP_KEY_NOT_FOUND );
	}

	// insert 16 values with kmkim0dd again
	for (int i=0; i<16; i++)
	{
		sprintf( key, "kmkim%03d", i);
		sprintf( value, "loves hyunsook %03d times. 2nd insertion.", i);
		ASSERT_TRUE( table_->putDataDesc( stgKey(key), &dataDesc ) == 0 );
		ASSERT_TRUE( table_->setDeletedBit( VIEW_VERSION, 0 /* savepoint Id */, dataDesc, true ) == 0 );
	}
	
	// verify 16 values with kmkim0dd
	verify_table_key_values( VIEW_UNCOMITTED_VERSIONS, table_, 16, "kmkim%03d", "loves hyunsook %03d times. 2nd insertion.");
}

/** @brief Test getDataDesc function.
 */
TEST_F(TableTest, DISABLED_DataDescManagement) {
	stgDataDesc * dataDesc;

	for (int i=1000; i>=0; i-- )
	{
		// TODO : BUGBUG : memory leak. Need to delete[] keyStr
		char * keyStr = new char[256]; 
		bool canModify;
		
		sprintf(keyStr, "%d-key", i);
		stgKey key(keyStr);

// The pseudo transaction address. Use this for checking if a key is associated with a correct stgDataDesc.
#define PSEUDO_TX_ADDR(n) ((void*)((n*8) + 8))
		
		// Get a key that does not exist
		dataDesc = NULL;
		ASSERT_TRUE( table_->getDataDesc( key, &dataDesc) == 0 );
		ASSERT_TRUE( dataDesc == NULL );

		// Put a key that does not exist
		ASSERT_TRUE( table_->putDataDesc( key, &dataDesc) == 0 );
		// Set the pseudo transaction address
		ASSERT_TRUE( dataDesc->tryModification(PSEUDO_TX_ADDR(i), &canModify) == 0 );
		ASSERT_TRUE( canModify );

		// Put a key that exists : dataDesc points to the existing key.
		ASSERT_TRUE( table_->putDataDesc( key, &dataDesc) == 0 );
		// Check the pseudo transaction address
		ASSERT_TRUE( dataDesc->isModifyingTransaction( PSEUDO_TX_ADDR(i) ) );

		// Get a key that exists
		dataDesc = NULL;
		ASSERT_TRUE( table_->getDataDesc( key, &dataDesc) == 0 );
		// Check the pseudo transaction address
		ASSERT_TRUE( dataDesc->isModifyingTransaction( PSEUDO_TX_ADDR(i) ) );
	}
}

/** @brief Test rollback to savepoint, commit.
 */
TEST_F(TableTest, DISABLED_Insert_Update_Rollback_Commit) 
{
	stgDataDesc * dataDesc;
	stgData foundData;

	// Put a key with a data descriptor.
	{
		ASSERT_TRUE( table_->putDataDesc( stgKey("theKey"), &dataDesc ) == 0 );
	}
	
	// Update data twice by increasing savepoint nId
	{
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp0") ) == 0 );
		ASSERT_TRUE( table_->updateData( 1/* view version */, 1 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp1") ) == 0 );

		// No data is found with the view version 1, because the changes are not comitted.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
	
		// When MAX_DATA_VERSION is specified, the uncomitted changes are searched.
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v002_sp1") );
	}
	
	// Rollback to savepoint 1
	{
		ASSERT_TRUE( dataDesc->removeUncomittedVersions( 1 /* savepint Id */ ) == 0 );
		
		// No data is found with the view version 1, because the changes are not comitted.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
		
		// When MAX_DATA_VERSION is specified, the uncomitted changes are searched.
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v002_sp0") );
	}

	// Commit changes
	{
		ASSERT_TRUE( dataDesc->commitChanges( 2 /* commit version */) == 0 );


		// No data is found with the view version 1, because it can't see a data version 2.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
		
		// The latest data is found with the view version 2, because the changes are comitted.
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v002_sp0") );
	}
	
	// If view version in updateData interface is lower than the latest comitted version, assertion hits.
	{
		//ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp2") ) == 0 );
	}
	
	// After committing the changes, savepoint Id begins from 0 again.
	// Rollback to savepoint 0
	{
		ASSERT_TRUE( table_->updateData( 2/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp0") ) == 0 );

		ASSERT_TRUE( table_->updateData( 2/* view version */, 1 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp1") ) == 0 );

		ASSERT_TRUE( table_->updateData( 2/* view version */, 2 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp2") ) == 0 );
		
		ASSERT_TRUE( dataDesc->removeUncomittedVersions( 0 /* savepint Id */ ) == 0 );
		
		// No data is found with the view version 2, because the changes are not comitted.
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );

		// When MAX_DATA_VERSION is specified, the uncomitted changes are searched.
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v002_sp2") );

		ASSERT_TRUE( dataDesc->removeUncomittedVersions( 0 /* savepint Id */ ) == 0 );

		// No data is found with any version, because changes are rolled back.
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
	}
}


/** @brief Test deleting a row, rollback, commit.
 */
TEST_F(TableTest, DISABLED_Insert_Delete_Rollback_Commit) 
{
	stgDataDesc * dataDesc;
	stgData foundData;

	// Put a key with a data descriptor.
	{
		ASSERT_TRUE( table_->putDataDesc( stgKey("theKey"), &dataDesc ) == 0 );
	}
	
	// Update data, increase savepoint Id, delete it
	{
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v002_sp0") ) == 0 );
		ASSERT_TRUE( table_->setDeletedBit( 1/* view version */, 1 /* savepoint Id */, dataDesc, true)  == 0 );

		// No data is found with the view version 1, because the changes are not comitted.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
	
		// When MAX_DATA_VERSION is specified, the uncomitted changes are searched, but no key found because it is deleted.
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
	}
	
	// Rollback to savepoint 1
	{
		ASSERT_TRUE( dataDesc->removeUncomittedVersions( 1 /* savepint Id */ ) == 0 );
		
		// No data is found with the view version 1, because the changes are not comitted.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
		
		// When MAX_DATA_VERSION is specified, the uncomitted changes are searched.
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v002_sp0") );
	}

	// Delete the data, Commit changes
	{
		// Savepoint 1 begins again.
		ASSERT_TRUE( table_->setDeletedBit( 1/* view version */, 1 /* savepoint Id */, dataDesc, true)  == 0 );
		
		ASSERT_TRUE( dataDesc->commitChanges( 2 /* commit version */) == 0 );

		// No data is found with the view version 1, because it can't see a data version 2.
		ASSERT_TRUE( table_->seekData( 1/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
		
		// The latest data is found with the view version 2, but it is marked as deleted.
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );
	}
	
	// Insertion happens again.
	{
		ASSERT_TRUE( table_->updateData( 2/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v003") ) == 0 );

		ASSERT_TRUE( dataDesc->commitChanges( 3 /* commit version */) == 0 );

		// No data is found with the view version 2, because the deleted bit is still set for version 2.
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == KD_EXCP_KEY_NOT_FOUND );

		// The latest data is found with the view version 3, and the newly insert data is seen.
		ASSERT_TRUE( table_->seekData( 3/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v003") );
	}

	// Deletion, Insertion happens within a transaction.
	{
		// Set the delete bit
		ASSERT_TRUE( table_->setDeletedBit( 3/* view version */, 0 /* savepoint Id */, dataDesc, true) == 0 );
		ASSERT_TRUE( table_->updateData( 3/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v004") ) == 0 );
		// Do it once more.
		ASSERT_TRUE( table_->setDeletedBit( 3/* view version */, 0 /* savepoint Id */, dataDesc, true) == 0 );
		ASSERT_TRUE( table_->updateData( 3/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("v004") ) == 0 );

		ASSERT_TRUE( dataDesc->commitChanges( 4 /* commit version */) == 0 );

		// The data is found with the view version 3.
		ASSERT_TRUE( table_->seekData( 3/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v003") );

		// The latest data is found with the view version 4, and the newly insert data is seen.
		ASSERT_TRUE( table_->seekData( 4/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "v004") );
	}

}

/** @brief Test in-place update, replacing a version to store a larger data.
 */
TEST_F(TableTest, DISABLED_Update) 
{
	const char * VERY_LARGE_STRING_v2 = "a_123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
	const char * VERY_LARGE_STRING_v3 = "b_123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
	stgDataDesc * dataDesc;
	stgData foundData;

	// Put a key with a data descriptor.
	{
		ASSERT_TRUE( table_->putDataDesc( stgKey("theKey"), &dataDesc ) == 0 );
	}

	// Update the data within the same savepoint
	{
		// update data with "1234567890"
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("1234567890") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "1234567890") );

		// update data with "12345"
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("12345") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "12345") );

		// update data with ""
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "") );
		
		// update data with a very large string
		ASSERT_TRUE( table_->updateData( 1/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA(VERY_LARGE_STRING_v2) ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, VERY_LARGE_STRING_v2) );
	}
	
	// Commit, verify the data
	{
		ASSERT_TRUE( dataDesc->commitChanges( 2 /* commit version */) == 0 );
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, VERY_LARGE_STRING_v2) );
	}

	// Update the data with a new savepoint
	{
		// update data with "1234567890"
		ASSERT_TRUE( table_->updateData( 2/* view version */, 0 /* savepoint Id */, dataDesc, KD_STR_DATA("1234567890") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "1234567890") );

		// update data with "12345"
		ASSERT_TRUE( table_->updateData( 2/* view version */, 1 /* savepoint Id */, dataDesc, KD_STR_DATA("12345") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "12345") );

		// update data with ""
		ASSERT_TRUE( table_->updateData( 2/* view version */, 2 /* savepoint Id */, dataDesc, KD_STR_DATA("") ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, "") );
		
		// update data with a very large string
		ASSERT_TRUE( table_->updateData( 2/* view version */, 3 /* savepoint Id */, dataDesc, KD_STR_DATA(VERY_LARGE_STRING_v3) ) == 0 );
		ASSERT_TRUE( table_->seekData( MAX_DATA_VERSION/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, VERY_LARGE_STRING_v3) );
	}

	// Commit, verify the data
	{
		ASSERT_TRUE( dataDesc->commitChanges( 3 /* commit version */) == 0 );
		ASSERT_TRUE( table_->seekData( 2/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, VERY_LARGE_STRING_v2) );
		ASSERT_TRUE( table_->seekData( 3/* view version */, dataDesc, &foundData) == 0 );
		ASSERT_TRUE( isDataEqual(&foundData, VERY_LARGE_STRING_v3) );
	}

}


// TODO : Assertion Hit - setting deleted bit to true without calling updateData interface.